﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LostLib
{
	/// <summary>
	/// This class implements item caching using WeakReference class.
	/// </summary>
	/// <typeparam name="T">Type of item to cache.</typeparam>
	/// <remarks>This class treats null as noncached and tries to recreate item.</remarks>
	public class Cached<T>
		where T: class
	{
		/// <summary>
		/// Creates an instance of cached value.
		/// </summary>
		/// <param name="getter">A function that moves value to cache.</param>
		/// <param name="cache">True if value must be cached immediately, else false.</param>
		public Cached(Func<T> getter, bool cache)
		{
			if (getter == null)
				throw new ArgumentNullException("getter");

			_reference = new WeakReference(null);
			_getter = getter;

			if (cache)
				_reference.Target = getter();
		}

		public Cached(Func<T> getter, T value): this(getter)
		{
			_reference.Target = value;
		}

		/// <summary>
		/// Creates an instance of cached value without initial caching.
		/// </summary>
		/// <param name="getter">A function that moves value to cache.</param>
		public Cached(Func<T> getter): this(getter, false)
		{
		}

		readonly WeakReference _reference;
		readonly Func<T> _getter;

		/// <summary>
		/// True if item is cached, otherwise false.
		/// </summary>
		public bool IsCached
		{
			get
			{
				var item = _reference.Target as T;
				return item != null;
			}
		}

		/// <summary>
		/// Gets cached value or null.
		/// </summary>
		/// <returns>null when item is not cached, else item from cache.</returns>
		public T TryGetValue()
		{
			return _reference.Target as T;
		}

		/// <summary>
		/// Gets a value of item.
		/// </summary>
		/// <param name="item">The cached item to get.</param>
		/// <returns>Cached value or newly generated value depending on its caching state.</returns>
		public static implicit operator T(Cached<T> item)
		{
			var result = item._reference.Target as T;
			if (result == null)
			{
				result = item._getter();
				item._reference.Target = result;
			}
			return result;
		}
	}
}
